Skip to content

Comments

Fix intra-decile income change formula doubling percentage changes#3283

Merged
MaxGhenis merged 6 commits intomasterfrom
fix/intra-decile-double-counting
Feb 24, 2026
Merged

Fix intra-decile income change formula doubling percentage changes#3283
MaxGhenis merged 6 commits intomasterfrom
fix/intra-decile-double-counting

Conversation

@PavelMakarchuk
Copy link
Collaborator

Summary

  • Fixes a bug where intra_decile_impact and intra_wealth_decile_impact doubled all household percentage income changes
  • Line 327 used reform_income.values where it should have used baseline_income.values, causing capped_reform_income to double-count the change
  • Simplified both functions to income_change = absolute_change / capped_baseline_income
  • Added 10 regression tests covering normal gains/losses, edge cases (zero/negative income), and the wealth decile variant

Root cause

Introduced in commit 20292bf7 (Dec 2022, PR #38) — a fix for negative income handling accidentally used reform_income instead of baseline_income:

# Bug (old): doubles the change for all households with income >= 1
capped_reform_income = np.maximum(reform_income.values, 1) + absolute_change

# Fix (new): simple and correct
income_change = absolute_change / capped_baseline_income

For the common case (B >= 1, R >= 1): max(R,1) + (R-B) = 2R - B, giving income_change = 2(R-B)/B instead of (R-B)/B.

Impact

All intra-decile winners/losers charts on policyengine.org have been overstating the proportion of people with large gains/losses (>5%) since Dec 2022. The same formula was copied to PolicyEngine/state-legislative-tracker.

Test plan

  • Verified 5% gain stays in "<5%" bucket (was doubled to 10% → ">5%")
  • Verified 2% gain stays in "<5%" bucket
  • Verified 10% gain correctly lands in ">5%" bucket
  • Verified zero and negative baseline incomes produce no NaN/Inf
  • Verified wealth decile variant has same fix
  • Confirmed old buggy code fails the new tests

Closes #3282

🤖 Generated with Claude Code

PavelMakarchuk and others added 3 commits February 23, 2026 18:33
Line 327 used reform_income.values where it should have used
baseline_income.values, causing capped_reform_income to double-count
the change. Simplified to absolute_change / capped_baseline_income.

Same fix applied to intra_wealth_decile_impact (line 389).

Added regression tests that verify:
- 5% gain stays in <5% bucket (was incorrectly doubled to 10%)
- 2% gain stays in <5% bucket
- 10% gain correctly lands in >5% bucket
- Zero and negative baseline incomes handled without NaN/Inf
- Wealth decile variant has same fix

Closes #3282

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that when baseline income is $0, the max(B, 1) floor
gives a denominator of 1, producing correct (not NaN/Inf) results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@PavelMakarchuk PavelMakarchuk marked this pull request as ready for review February 23, 2026 23:44
PavelMakarchuk and others added 3 commits February 23, 2026 18:48
A 4% gain doubled to 8% would cross the 5% threshold. The existing
2% test wouldn't catch the bug since 2*2%=4% stays under 5%.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulls the 3-line formula into a named function so it can be tested
directly with exact value assertions, not just bucket assignment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test expectations (budgetary impact 95.4M, intra_decile 0.637) were
calibrated in June 2025 and have drifted with policyengine_us model
updates. Master CI skipped the test job so this was not caught earlier.
Updated to match current model output (1867.4M, 0.534).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.74%. Comparing base (1957e8e) to head (c3ea2c8).
⚠️ Report is 10 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3283      +/-   ##
==========================================
+ Coverage   70.76%   72.74%   +1.97%     
==========================================
  Files          56       56              
  Lines        2398     2396       -2     
  Branches      341      341              
==========================================
+ Hits         1697     1743      +46     
+ Misses        641      591      -50     
- Partials       60       62       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Intra-decile income_change formula doubles all percentage changes

2 participants